﻿using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mmeller.Net.Decoder
{
    class Engine<T> : ICryptoEngine where T : IBlockCipher
    {
        private CryptoMode mode;
        private PaddedBufferedBlockCipher cipher;

        public Engine() : this(CryptoMode.ECB, 0) { }
        public Engine(CryptoMode mode, int blockLength)
        {
            Type t = typeof(T);
            this.mode = mode;

            setMode(blockLength, t);
        }

        private void setMode(int blockLength, Type t)
        {
            switch (this.mode)
            {
                case CryptoMode.ECB:
                    this.cipher = new PaddedBufferedBlockCipher((IBlockCipher)Activator.CreateInstance(t));
                    break;
                case CryptoMode.CBC:
                    this.cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher((IBlockCipher)Activator.CreateInstance(t)));
                    break;
                case CryptoMode.OFB:
                    this.cipher = new PaddedBufferedBlockCipher(new OfbBlockCipher((IBlockCipher)Activator.CreateInstance(t), blockLength));
                    break;
                case CryptoMode.CFB:
                    this.cipher = new PaddedBufferedBlockCipher(new CfbBlockCipher((IBlockCipher)Activator.CreateInstance(t), blockLength));
                    break;


            }
        }

        public void init(int sessionKeyLength, byte[] sessionKey, byte[] initialVector = null)
        {
            switch (this.mode)
            {
                case CryptoMode.ECB:
                    this.cipher.Init(true, new KeyParameter(sessionKey));
                    break;
                default:
                    KeyParameter parametr = new KeyParameter(sessionKey);
                    ParametersWithIV iv = new ParametersWithIV(parametr, initialVector);
                    this.cipher.Init(true, iv);
                    break;

            }
        }

        public byte[] processData(byte[] inputData)
        {
            byte[] outputData = new byte[this.cipher.GetOutputSize(inputData.Length)];

            int processedBytes = this.cipher.ProcessBytes(inputData, 0, inputData.Length, outputData, 0);
            return outputData;
        }

        public byte[] finalProcessData(int blockSize)
        {
            byte[] outputData = new byte[blockSize];
            int processedBytes = this.cipher.DoFinal(outputData, 0);

            if (processedBytes > 0)
            {
                return outputData;
            }
            else
            {
                return null;
            }
        } 
    }
}
